home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
EnigmA Amiga Run 1996 March
/
EnigmA AMIGA RUN 05 (1996)(G.R. Edizioni)(IT)[!][issue 1996-03][Skylink CD IV].iso
/
earcd
/
comm2
/
httpprxy.lha
/
httpproxy
/
httpproxy.c
< prev
next >
Wrap
C/C++ Source or Header
|
1995-12-07
|
58KB
|
1,959 lines
/*(( "Header" */
/*
* $Id: httpproxy.c,v 0.9 1995/12/06 19:53:55 mshopf Exp mshopf $
*
* (c) 1995 Matthias Hopf
*
* A small little Http Proxy.
* It can serv as a ProxyProxy, too (i.e. it can perform only caching and will
* get its data from another proxy).
* That way it can be used for other protocol types than html, too.
*
* Run it standalone at high priority. It won't need much computing time as
* it does no busy wait at all.
* If you really want to re-get an already cached page, just reload it immedeately.
* (no other request inbetween and no more than ReloadTime seconds delay).
* If you browse offline, you'll get a note that the cache is invalid. Reload the
* page if you want to queue the page and get the old cache.
*/
/*
* $Log: httpproxy.c,v $
* Revision 0.9 1995/12/06 19:53:55 mshopf
* added fixes for unix machines and AmiTCP4.0 compilation.
* some bug fixes.
* more html conform now.
* lots of printf type fixes.
*
* Revision 0.8 1995/12/03 14:20:23 mshopf
* Made auto requests and proxy messages more http conform.
*
* Revision 0.7 1995/11/19 17:57:50 mshopf
* added revbump compatible version string.
* fixed ftp offline proxyproxy std port bug.
*
* Revision 0.6 1995/11/04 11:26:13 mshopf
* better shutdown (requeueing of current transmissions). small bug fixes.
*
* Revision 0.5 1995/11/02 18:26:13 mshopf
* queueing system implemented.
*
* Revision 0.4 1995/10/21 21:28:37 mshopf
* small bug fix.
*
* Revision 0.3 1995/10/17 19:23:09 mshopf
* cleaned up messy logging system.
* new option 'log'.
*
* Revision 0.2 1995/10/13 18:09:17 mshopf
* everything works so far, exceptions are noted in the header.
* :-)
*
* Revision 0.1 1995/10/13 10:44:27 mshopf
* no caching at all right now, but it works fine as a proxyproxy.
*
*/
/*)) */
/*(( "Includes" */
#include "httpproxy_rev.h"
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <errno.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include <time.h>
#include <ctype.h>
#include <syslog.h>
#include <dirent.h>
#include <sys/stat.h>
#include <sys/syslog.h>
#include <sys/types.h>
#include <sys/time.h>
#include <sys/ioctl.h>
#include <sys/socket.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#ifdef _AMIGA
# include <bsdsocket.h>
# define ioctl IoctlSocket
# define ioctl_t long
# define strcasecmp stricmp
# ifdef sys_errlist
# define strerror(x) (sys_errlist [x]) /* Won't necessary for AmiTCP4.0 */
# endif
# define CURRENTDIR ""
typedef long len_t;
#else
# define ioctl_t int
# define CloseSocket close
# define CURRENTDIR "."
typedef int len_t;
#endif
#ifdef DEBUG
# define debug(x) printf x
#else
# define debug(x) (0)
/*# define NDEBUG*/
#endif
#include <assert.h>
/*)) */
/*(( "Types / Variables / Constants" */
/* Types and constants */
#define CHECK_ADDRESS FALSE /* set this to TRUE, when you want to look up all addresses
* (this is only important in proxyproxy mode, otherwise all
* addresses need to be looked up anyway). If set to false,
* it may be faster, but the proxy will not recognize a.b as a.b.c
* and it cannot recognice alias names...
* Note: Right now it is not wise to set this when you do not have
* a personal name server, a name caching system or *big* hosts file. */
#define DEFAULT_PROXYPORT 8080
#define DEFAULT_HTTPPORT 80
#define DEFAULT_DELTIME (48*60*60) /* default: delete cached files on startup */
#define DEFAULT_EXPIRETIME (24*60*60) /* default: delete cache, when the page is requested and too old */
#define DEFAULT_RELOADTIME 10 /* default: expire cache on reload inbetween */
#define MAX_REQUESTS 8 /* Maximum number of pending requests */
#define MIN_REQUESTS 4 /* Minimum number of free requests for interactive actions */
#define MAX_URLBUFFER 1024 /* currently no Url request may be larger than this value! */
#define MAX_URLSAVE 128 /* maximum size of saved url requests */
#define MAX_FILENAME 20 /* maximum size of filename */
#define MAX_CACHES 1024 /* maximum number of cache slots */
#define MAX_DATABUFFER 2048
#define SHIFT_DATABUFFER 512 /* when x bytes are in the data buffer, shift it after send() */
#define REQ_REQSOCKET 0x01 /* a request socket is open */
#define REQ_CONNSOCKET 0x02 /* a connection socket is open / to be connected */
#define REQ_DONE 0x04 /* the connection socket is already closed */
#define REQ_URLDONE 0x08 /* the URL is completely read (cache may be sent) */
#define REQ_HTTP1X0 0x10 /* the URL was sent with HTTP/1.0 */
#define CACHE_VALID 0x01 /* Cache is filled and ready to serve */
#define CACHE_QUEUED 0x02 /* Cache is already queued for regetting */
#define CACHE_DELETETMP 0x04 /* There's a temporaray cache file to be removed */
#ifndef FALSE
#define FALSE (0)
#define TRUE (1)
#endif
/* Needed Memory */
#define NEED_MEM ((MAX_REQUESTS * sizeof (request_t) + MAX_CACHES * sizeof (cache_t)) / 1024)
#define difftime(x,y) (((u_long)(x)) - ((u_long)(y)))
#define isvalidhttp(x) ((unsigned char)(x) > ' ' && (x) != '%' && (x) != '\\' && (unsigned char)(x) < 128)
/*
* Some typical Flag combinations in typical order:
* None: Empty slot
* REQ_REQSOCKET: Awaiting URL
* REQ_REQSOCKET | REQ_CONNSOCKET: Still geting URL, but 1. line is already there
* *or* URL done
* Awaiting connection to remote host
* *or* already transfering data
* REQ_REQSOCKET | REQ_DONE: The connection socket is already closed, but data is
* still to be delivered, or data is sent from the cache
* REQ_CONNSOCKET: Request was terminated, still geting data to fill up
* the cache (TIMEOUT needed!!!)
*/
/* REQ_REQSOCKET: alone will never occour on ProxyProxy==TRUE... */
typedef struct {
int Flags;
char File [MAX_FILENAME]; /* "" if free slot / with begining '_' (data) */
char Url [MAX_URLSAVE]; /* "" if free slot or just getting data */
} cache_t;
typedef struct {
long ReqSocket, ConnSocket;
int Flags;
char UrlBuffer [MAX_URLBUFFER];
char DataBuffer [MAX_DATABUFFER];
int UrlSent, UrlRecv;
int DataSent, DataRecv;
cache_t *Cache; /* NULL if not cacheable */
FILE *Stream; /* != NULL on open data transfer to/from cache */
} request_t;
/* Global variables */
char *VVersion= VERSTAG;
char *Version = VSTRING;
FILE *LogStream;
char *PrgName;
long ServerSocket;
int StdHttpPort;
int ServerPort = DEFAULT_PROXYPORT;
int ProxyProxy = FALSE; /* TRUE, when all requests should be
* forwarded to another proxy. */
struct sockaddr_in ProxyProxyIn;
cache_t *LastCache = NULL;
time_t ThisRequestTime = 0;
time_t StartUpTime;
int RequestsFree = MAX_REQUESTS;
int CachesFree = MAX_CACHES;
long CacheNr = 0;
u_long DelCacheTime = DEFAULT_DELTIME;
u_long ExpireCacheTime = DEFAULT_EXPIRETIME;
u_long ReloadCacheTime = DEFAULT_RELOADTIME;
int OffLine = 0; /* 0: get cache files again, when they are too old */
/* 1: keep cache files and queue them */
int GetQueued = 0; /* 1: get queued data from remote hosts or proxyproxy */
int CacheUnreadRequests = FALSE; /* 1: keep cache data on data connection close with data in url send buffer */
request_t *Requests;
cache_t *Caches;
void DeleteConnect (request_t *Req, int ok);
void SaveCacheUrl (cache_t *c);
/*)) */
/*(( "Init ()" */
/* Init all global variables, open server port */
void Init (char *ProxyProxyHost, int ProxyProxyPort, char *LogName)
{
struct servent *ServEnt;
struct sockaddr_in SockIn;
struct hostent *HostEnt;
ioctl_t on = 1;
/* Standard setups */
if (! (LogStream = fopen (LogName, "a+")) )
{
fprintf (stderr, "cannot open logfile '%s': %s\n", LogName, strerror (errno));
exit (20);
}
if (! (Requests = calloc (MAX_REQUESTS, sizeof (request_t))) )
{
fprintf (stderr, "not enough memory (need %d Kbyte)\n", NEED_MEM);
exit (20);
}
if (! (Caches = calloc (MAX_CACHES, sizeof (cache_t))) )
{
fprintf (stderr, "not enough memory (need %d Kbyte)\n", NEED_MEM);
exit (20);
}
ThisRequestTime = StartUpTime = time (NULL);
/* Get standard HTTP port (80 at the time of development...but you never know) */
if (! (ServEnt = getservbyname ("http", "tcp")) )
{
StdHttpPort = DEFAULT_HTTPPORT;
syslog (LOG_WARNING, "%s: unknown protocol 'http', using default port %d", PrgName, DEFAULT_HTTPPORT);
}
else
StdHttpPort = ServEnt->s_port;
/* Create Serversocket */
if ( (ServerSocket = socket (PF_INET, SOCK_STREAM, 0)) < 0)
{
fprintf (stderr, "socket() for serverport failed: %s\n", strerror (errno));
exit (20);
}
memset ((char *) &SockIn, 0, sizeof (struct sockaddr_in));
SockIn.sin_family = AF_INET;
SockIn.sin_addr.s_addr = INADDR_ANY;
SockIn.sin_port = htons (ServerPort);
if (bind (ServerSocket, (struct sockaddr *) &SockIn, sizeof (struct sockaddr_in)) < 0)
{
fprintf (stderr, "bind() failed for port %d: %s\n", ServerPort, strerror (errno));
exit (20);
}
#ifdef FIOASYNC
if (ioctl (ServerSocket, FIOASYNC, (caddr_t) &on) < 0)
{
fprintf (stderr, "ioctl() failed for FIOASYNC: %s\n", strerror (errno));
exit (20);
}
#endif
#ifdef FIONBIO
if (ioctl (ServerSocket, FIONBIO, (caddr_t) &on) < 0)
{
fprintf (stderr, "ioctl() failed for FIONBIO: %s\n", strerror (errno));
exit (20);
}
#endif
if (listen (ServerSocket, 4) < 0)
{
fprintf (stderr, "listen() failed: %s\n", strerror (errno));
exit (20);
}
/* Get ProxyProxy address */
if (ProxyProxyHost)
{
ProxyProxy = TRUE;
if (! OffLine)
{
memset ((char *) &ProxyProxyIn, 0, sizeof (struct sockaddr_in));
ProxyProxyIn.sin_family = AF_INET;
ProxyProxyIn.sin_port = htons (ProxyProxyPort);
if ( (ProxyProxyIn.sin_addr.s_addr = inet_addr (ProxyProxyHost)) +0 == -1) /* !!! */
{
if (! (HostEnt = gethostbyname (ProxyProxyHost)) )
{
fprintf (stderr, "can't get proxyproxy host '%s'\n", ProxyProxyHost);
exit (20);
}
else
ProxyProxyIn.sin_addr.s_addr = ((struct in_addr *)(HostEnt->h_addr)) -> s_addr;
}
fprintf (LogStream, "%sstarting with proxyproxy host '%s', port %d\n",
Version, ProxyProxyHost, ProxyProxyPort);
debug (("%sstarting with proxyproxy host '%s', port %d\n",
Version, ProxyProxyHost, ProxyProxyPort));
return;
}
}
else if (! OffLine)
{
fprintf (LogStream, "%sstarting in normal mode\n", Version);
debug (("%sstarting in normal mode\n", Version));
return;
}
fprintf (LogStream, "%sstarting in offline mode\n", Version);
debug (("%sstarting in offline mode\n", Version));
}
/*)) */
/*(( "InitCacheSlot()/GetFreeCacheSlot()/ErrToReq()" */
/* Initialize cache slot */
void InitCacheSlot (cache_t *c, cache_t *Template, char Init, char Separator)
{
if (! Template)
{
c->Flags = 0;
c->Url[0] = '\0';
sprintf (c->File, "%c%08lx%c%08lx", Init, StartUpTime, Separator, CacheNr++);
debug (("new cache allocated: '%s'\n", c->File));
}
else
{
c->Flags = Template->Flags & ~CACHE_VALID;
strcpy (c->Url, Template->Url);
sprintf (c->File, "%c%8.8s%c%8.8s", Init, & Template->File [1], Separator, & Template->File [10]);
debug (("cache %s from template %s\n", c->File, Template->File));
}
}
/* Scan for a free cache slot */
cache_t *GetFreeCacheSlot (void)
{
cache_t *c;
int i;
debug (("new cache requested.\n"));
if (! CachesFree)
return (NULL);
for (c = Caches, i=0; i < MAX_CACHES; c++, i++)
if (c->Url[0] == '\0' && c->File[0] == '\0')
{
CachesFree--;
InitCacheSlot (c, NULL, '_', '.');
return (c);
}
assert (0); /*NOTREACHED*/
}
/* Type an error to a data buffer of a pending request and set everything up.
* The error is printed into the Logstream, too. When Short == NULL, a information
* message is sent, and nothing is printed into the Logstream. */
void ErrToReq (request_t *Req, int Number, char *Short, char *Descr)
{
static char *Author = "<A HREF=\"http://wwwcip.informatik.uni-erlangen.de/user/mshopf\">Matthias Hopf</A>";
Req->DataSent = 0;
if (Short)
{
fprintf (LogStream, "#%02d: Error: %s\n", (int) Req->ReqSocket, Short);
debug (("#%02d: Error: %s\n", (int) Req->ReqSocket, Short));
sprintf (Req->DataBuffer, "HTTP/1.0 %03d %s\015\n"
"Server: Httpproxy_V%d.%d\015\n"
"Content-Type: text/html\015\n\015\n"
"<HTML><H1>Proxy Error: %s</H1><P>\n"
"%s<P>\n"
"<HR><ADDRESS>%s by %s</ADDRESS>\n",
Number, Short, VERSION, REVISION, Short, Descr, Version, Author);
}
else
sprintf (Req->DataBuffer, "HTTP/1.0 %03d Proxy Message\015\n"
"Server: Httpproxy_V%d.%d\015\n"
"Content-Type: text/html\015\n\015\n"
"<HTML><H3>Proxy Message:</H3><P>\n"
"%s<P>\n"
"<HR><ADDRESS>%s by %s</ADDRESS>\n",
Number, VERSION, REVISION, Descr, Version, Author);
Req->DataRecv = strlen (Req->DataBuffer);
Req->Flags |= REQ_DONE;
if (Req->Flags & REQ_CONNSOCKET)
DeleteConnect (Req, FALSE);
}
/*)) */
/*(( "RemCacheEntry()/CheckCacheTime()" */
/* Remove a cache entry and its according files */
void RemCacheEntry (cache_t *c)
{
cache_t tmp;
debug (("deleting cache file '%s', additional url: %s\n", c->File, c->Url[0] ? "yes" : "no"));
if (c->File[0] && c->File[0] != '.') /* It's no special message file */
{
if (remove (c->File))
{
fprintf (LogStream, "cannot delete invalid cache file '%s': %s\n", c->File, strerror (errno));
debug (("cannot delete invalid cache file '%s': %s\n", c->File, strerror (errno)));
}
if (c->Url[0])
{
c->File[0] = '@';
if (remove (c->File))
{
fprintf (LogStream, "cannot delete invalid cache file '%s': %s\n", c->File, strerror (errno));
debug (("cannot delete invalid cache file '%s': %s\n", c->File, strerror (errno)));
}
}
if (c->Flags & CACHE_DELETETMP)
{
InitCacheSlot (&tmp, c, '@', '@');
debug (("removing temporary url file '%s'\n", tmp.File));
if (remove (tmp.File))
{
fprintf (LogStream, "error while removing temporary url file '%s': %s\n", tmp.File, strerror (errno));
debug (("error while removing temporary url file '%s': %s\n", tmp.File, strerror (errno)));
}
}
}
c->File[0] = c->Url[0] = '\0';
c->Flags = 0;
CachesFree++;
}
/* Check the modification time and date of a cache entry; set Time to 0 to force expire. */
/* Returns -1, when the cache entry is expired or to be reloaded or not existing, the entry is removed.
* Returns -2, when the cache entry is expired and queued. */
int CheckCacheTime (cache_t *c, u_long Time)
{
time_t FileT = -1;
struct stat s;
if (stat (c->File, &s) >= 0)
{
FileT = s.st_mtime;
debug (("local 0x%lx, access 0x%lx, diff1 %ld <?> %s\n", ThisRequestTime, FileT,
difftime (ThisRequestTime, FileT), difftime (ThisRequestTime, FileT) > Time ? "expired" : "valid"));
if ((! Time) || difftime (ThisRequestTime, FileT) > Time)
{
if (OffLine)
{
if (! (c->Flags & CACHE_QUEUED))
{
cache_t tmp;
c->Flags |= CACHE_QUEUED;
assert (c->Flags & CACHE_VALID);
InitCacheSlot (&tmp, c, '@', '@'); /* Queue Url for cache entry and return -2 */
SaveCacheUrl (&tmp);
}
return (-2);
}
else if (c->Flags & CACHE_QUEUED) /* Is the entry queued and not yet sent? */
return (-2);
else
FileT = -1;
}
}
if (FileT == -1)
{
if (c->Flags & CACHE_QUEUED)
{
debug (("cache entry '%s' not yet there (queued) - This should not happen...\n", c->File));
fprintf (stderr, "%s: Warning! Data consistency failure, line %d\n", PrgName, __LINE__);
return (-2);
}
else
{
debug (("cache entry '%s' expired / to be reloaded, removing\n", c->File));
RemCacheEntry (c);
return (-1);
}
}
return (c->Flags & CACHE_QUEUED ? -2 : 0);
}
/*)) */
/*(( "ReadCacheUrl()/SaveCacheUrl()" */
/* Read the Url from a specific url file */
void ReadCacheUrl (cache_t *c, char *Name)
{
FILE *f;
c->Url[0] = ' '; /* RemCacheEntry shall remove the url file, too, in case it is called */
switch (CheckCacheTime (c, DelCacheTime)) {
case 0:
case -3:
break;
case -1:
return;
default:
RemCacheEntry (c);
return;
}
if (! (f = fopen (Name, "r")) )
{
fprintf (LogStream, "cannot open url file '%s', removing cache entry: %s\n", Name, strerror (errno));
debug (("cannot open url file '%s', removing cache entry: %s\n", Name, strerror (errno)));
RemCacheEntry (c);
return;
}
if (fgets (c->Url, MAX_URLSAVE, f))
{
switch (c->Url [strlen (c->Url) - 1]) {
case '\n':
case '\015':
break;
default: /* Cache entry is valid -> return */
fclose (f);
c->Flags = CACHE_VALID;
debug (("valid cache entry: url '%s', data '%s'\n", c->Url, c->File));
return;
}
}
fclose (f);
fprintf (LogStream, "corrupt cache entry for url file '%s', removing\n", Name);
debug (("corrupt cache entry for url file '%s', removing\n", Name));
RemCacheEntry (c);
}
/* Save the cache's Url to its url file */
void SaveCacheUrl (cache_t *c)
{
FILE *UrlStream;
cache_t tmp;
c->File[0] = '@';
debug (("writing url file '%s'\n", c->File));
if ( (UrlStream = fopen (c->File, "w")) )
{
fprintf (UrlStream, "%s", c->Url); /* no error checking - well... */
fclose (UrlStream);
c->File[0] = '_';
if (! (c->Flags & CACHE_QUEUED))
c->Flags |= CACHE_VALID;
/* fprintf (LogStream, "URL '%s' is now cached\n",
* c->Url);*/
if (c->Flags & CACHE_DELETETMP)
{
InitCacheSlot (&tmp, c, '@', '@');
debug (("removing temporary url file '%s'\n", tmp.File));
if (remove (tmp.File))
{
fprintf (LogStream, "error while removing temporary url file '%s': %s\n", tmp.File, strerror (errno));
debug (("error while removing temporary url file '%s': %s\n", tmp.File, strerror (errno)));
}
c->Flags &= ~CACHE_DELETETMP;
}
}
else
{
fprintf (LogStream, "cannot write cache url file '%s': %s\n",
c->File, strerror (errno));
debug (("cannot write cache url file '%s': %s\n",
c->File, strerror (errno)));
c->File[0] = '_';
if (c->Flags & CACHE_QUEUED)
c->File[0] = '\0';
RemCacheEntry (c);
}
}
/*)) */
/*(( "BuildCache ()" */
/* Scan cache directory, build up cache and delete invalid cache entries */
void BuildCache (void)
{
DIR *Dir;
cache_t *c = Caches, *cc, *ccc;
struct dirent *d;
if (! (Dir = opendir (CURRENTDIR)))
{
fprintf (stderr, "%s: cannot open cache directory: %s\n", PrgName, strerror (errno));
exit (20);
}
errno = 0;
while ( (d = readdir (Dir)) )
{
if (strlen (d->d_name) > MAX_FILENAME-1)
{
if (d->d_name[0] != '.') /* no special file... */
fprintf (stderr, "unknown file '%s' in cache directory\n", d->d_name);
}
else
{
strcpy (c->File, d->d_name);
if (c->File [0] == '_') /* found cache data - look for cache url */
{
debug (("found data '%s'\n", c->File));
for (cc = Caches; cc != c; cc++)
if (strcmp (&c->File[1], &cc->File[1]) == 0 && cc->File[0] == '@')
{
cc->File[0] = '_';
c->File[0] = '@';
ReadCacheUrl (cc, c->File); /* already read... */
c->File[0] = '\0';
break;
}
if (c == cc) /* not found... */
{
c++; /* keep it, may be we'll get the url file, too */
if (! --CachesFree)
{
fprintf (LogStream, "Cache table full while recacheing\n");
debug (("Cache table full while recacheing\n"));
break; /* break from while - no room left... */
}
}
}
else if (c->File [0] == '@') /* found cache url - look for cache data */
{
debug (("found url '%s'\n", c->File));
for (cc = Caches; cc != c; cc++)
if (strcmp (&c->File[1], &cc->File[1]) == 0 && cc->File[0] == '_')
{
ReadCacheUrl (cc, c->File); /* already read... */
c->File[0] = '\0';
break;
}
if (c == cc) /* not found... */
{
c++; /* keep it, may be we'll get the data file, too */
if (! --CachesFree) /* otherwise it is a queued entry */
{
fprintf (LogStream, "Cache table full while recacheing\n");
debug (("Cache table full while recacheing\n"));
break; /* break from while - no room left... */
}
}
}
else
{
if (c->File [0] != '.') /* .files, .httpproxy-log or standard directories (unix) */
fprintf (stderr, "unknown file '%s' in cache directory\n", c->File);
c->File[0] = '\0';
}
}
}
if (errno)
fprintf (stderr, "directory error while building cache table, continueing: %s\n", strerror (errno));
closedir (Dir);
/* now remove all incomplete cachentries (and files) */
for (cc = Caches; cc != c; cc++)
if (cc->Url[0] == '\0' && cc->File [0] == '_')
{
fprintf (LogStream, "cache file '%s' invalid, removing.\n", cc->File);
debug (("cache file '%s' invalid, removing.\n", cc->File));
RemCacheEntry (cc);
}
/* mark all url only caches as queued */
for (cc = Caches; cc != c; cc++)
if (cc->File [0] == '@')
{
ReadCacheUrl (cc, cc->File); /* the file name remains '@...' */
if (cc->File [0] == '@')
{
cc->Flags = CACHE_QUEUED; /* and ! CACHE_VAILD */
for (ccc = Caches; ccc != c; ccc++)
if (ccc->File [0] == '_')
if (cc != ccc && strcmp (ccc->Url, cc->Url) == 0)
{
debug (("url '%s': queued url file %s associated with expired cache entry %s\n", cc->Url, cc->File, ccc->File));
assert (ccc->Flags & CACHE_VALID);
assert (cc->File [9] == '@'); /* it has to be a special queued url file... */
ccc->Flags |= CACHE_QUEUED | CACHE_DELETETMP;
cc->File[0] = '\0';
RemCacheEntry (cc);
break;
}
if (ccc == c)
debug (("queued url '%s'\n", cc->Url));
}
}
}
/*)) */
/*(( "ScanCache()" */
/* Scan whether a specific cache is already there, filled and ready to serve */
/* Returns 0: no, not there 1: yes, all set up -1: Failure
* 2: error message in buffer */
/* Sets up the request struct for correct filling, opens all streams, etc. */
int ScanCache (request_t *Req, char *Url, int Known)
{
int i;
cache_t *c;
static time_t LastRequestTime;
debug (("Searching for cache for Url '%s'\n", Url));
LastRequestTime = ThisRequestTime;
ThisRequestTime = time (NULL);
debug (("Last: %ld, This: %ld, Diff: %ld, reload: %s\n", LastRequestTime, ThisRequestTime,
difftime (ThisRequestTime, LastRequestTime),
difftime (ThisRequestTime, LastRequestTime) < ReloadCacheTime ? "yes" : "no"));
for (c = Caches, i=0; i < MAX_CACHES; c++, i++)
if (c->Url[0])
{
if (strcmp (c->Url, Url) == 0)
{
if (c->Flags & CACHE_VALID)
{
switch (CheckCacheTime (c, ExpireCacheTime)) {
case 0:
if (c == LastCache && difftime (ThisRequestTime, LastRequestTime) < ReloadCacheTime)
{
if (CheckCacheTime (c, 0) == -2) /* force queueing / reloading */
{
debug (("queued url on request\n"));
ErrToReq (Req, 203, NULL, "Your request for reloading the document is queued.<BR>\n"
"You will get the new document next time you are online.<BR>\n"
"An expired cache entry exists and can be viewed by immedeately reloading this document.");
return (2);
}
break; /* forcing reload of url */
}
if (! (Req->Stream = fopen (c->File, "r")) )
{
fprintf (LogStream, "cannot open cache file '%s', removing: %s\n", c->File, strerror (errno));
debug (("cannot open cache file '%s', removing: %s\n", c->File, strerror (errno)));
RemCacheEntry (c);
}
else
{
debug (("Found cache entry, file '%s'\n", c->File));
Req->Cache = LastCache = c; /* ok, found cache entry */
Req->Flags |= REQ_DONE;
return (1);
}
break;
/* case -1: break; */
case -2:
if (c == LastCache && difftime (ThisRequestTime, LastRequestTime) < ReloadCacheTime)
{
debug (("Sending (invalid) cache file '%s' on request\n", c->File));
if (! (Req->Stream = fopen (c->File, "r")) )
{
fprintf (LogStream, "cannot open cache file '%s', removing: %s\n", c->File, strerror (errno));
debug (("cannot open cache file '%s', removing: %s\n", c->File, strerror (errno)));
RemCacheEntry (c);
break;
}
else
{
Req->Cache = c; /* ok, send invalid cache entry */
Req->Flags |= REQ_DONE;
return (1);
}
}
else
{
debug (("Cache entry expired, queued\n"));
ErrToReq (Req, 203, NULL, "Your request is queued.<BR>\n"
"You will get the document next time you are online.<BR>\n"
"An expired cache entry exists and can be viewed by immedeately reloading this document.");
LastCache = c;
return (2);
}
}
}
else /* c->Flags & CACHE_VALID */
{
if (c->Flags & CACHE_QUEUED)
{
if (OffLine)
{
if (c == LastCache && difftime (ThisRequestTime, LastRequestTime) < ReloadCacheTime)
{
ErrToReq (Req, 404, "Already queued", "Your request is already queued.<BR>\n"
"You will get the document next time you are online.<BR>\n"
"You tried to get an expired cache entry, but this document is not cached right now.");
return (2);
}
else
{
debug (("Cache entry already queued\n"));
ErrToReq (Req, 203, NULL, "Your request is already queued.<BR>\n"
"You will get the document next time you are online.<BR>\n"
"There is no expired cache entry for this document.");
LastCache = c;
return (2);
}
}
else
{
debug (("unqueueing and getting Cache entry\n"));
c->Flags = 0;
c->File[0] = '_';
LastCache = c;
goto HaveAlreadyCache; /* sorry... */
}
}
else
{
debug (("Second request while getting URL\n"));
ErrToReq (Req, 204, NULL, "The same request was sent from another connection.\n"
"May be you requested a queued URL which is just being received.<BR>\n"
"Please try again in a few moments.");
LastCache = c;
return (2);
}
}
}
}
if (! (Known || ProxyProxy || OffLine)) /* not cached, unknown and online without proxyproxy... don't know where to get it */
{
ErrToReq (Req, 404, "Unknown host", "You tried to get a document from an unknown host. Please check the URL.");
debug (("#%02d: unknown host: URL '%s'\n", (int) Req->ReqSocket, Url));
return (2);
}
if (! (c = GetFreeCacheSlot ()) ) /* not in cache so far */
{
if (OffLine)
{
ErrToReq (Req, 500, "Cache table full", "I'm sorry, but the cache table is full.<BR>"
"It is not possible to queue up your request.");
return (2);
}
else
{
fprintf (LogStream, "Cache table full\n");
debug (("Cache table full\n"));
return (0); /* no more free entries - just proxy it */
}
}
if (OffLine)
{
ErrToReq (Req, 203, NULL, "Your new request is queued.<BR>\n"
"You will get the document next time you are online.");
debug (("Request queued.\n"));
strcpy (c->Url, Url);
c->Flags = CACHE_QUEUED;
LastCache = c;
SaveCacheUrl (c);
return (2);
}
HaveAlreadyCache:
if (! (Req->Stream = fopen (c->File, "w")) )
{
fprintf (LogStream, "cannot open new cache file '%s': %s\n", c->File, strerror (errno));
debug (("cannot open new cache file '%s': %s\n", c->File, strerror (errno)));
c->File[0] = '\0';
RemCacheEntry (c);
return (0); /* just proxy it */
}
debug (("New cache file '%s'\n", c->File));
strcpy (c->Url, Url); /* new cache entry */
c->Flags = 0;
Req->Cache = LastCache = c;
return (0);
}
/*)) */
/*(( "GetCacheData ()" */
/* Fill request data space with data from cache
* when a read error occures, no data will be received and ServWrite()
* will automagically terminate the socket. */
void GetCacheData (request_t *Req)
{
int Bytes;
assert (Req->DataRecv < MAX_DATABUFFER);
assert (Req->Stream);
debug (("Getting more cache data - Url done: %s\n", Req->Flags & REQ_URLDONE ? "yes" : "no"));
if ( (Bytes = fread (& Req->DataBuffer [Req->DataRecv], sizeof (char),
MAX_DATABUFFER - Req->DataRecv, Req->Stream)) > 0)
Req->DataRecv += Bytes;
else
if (! feof (Req->Stream))
{
fprintf (LogStream, "read error on cache file '%s': %s\n", Req->Cache->File, strerror (errno));
debug (("read error on cache file '%s': %s\n", Req->Cache->File, strerror (errno)));
}
debug (("Read %d bytes\n", Bytes));
}
/*)) */
/*(( "BuildFdSets ()" */
/* Build fdsets (outstanding reads and writes) */
void BuildFdSets (fd_set *ReadSet, fd_set *WriteSet)
{
int i;
request_t *Req;
FD_ZERO (ReadSet);
FD_ZERO (WriteSet);
debug (("Build: "));
if (RequestsFree)
{
debug (("Server %d", (int) ServerSocket));
FD_SET (ServerSocket, ReadSet);
}
for (i=0, Req=Requests; i < MAX_REQUESTS; i++, Req++)
{
if (Req->Flags & REQ_REQSOCKET)
{
if (Req->UrlRecv < MAX_URLBUFFER-1)
{
debug ((", Read Req %d", (int) Req->ReqSocket));
FD_SET (Req->ReqSocket, ReadSet);
}
if ((Req->Flags & (REQ_URLDONE | REQ_DONE))
&& Req->DataRecv > Req->DataSent) /* only write when the request ist finished */
{
debug ((", Write Req %d", (int) Req->ReqSocket));
FD_SET (Req->ReqSocket, WriteSet);
}
}
if (Req->Flags & REQ_CONNSOCKET)
{
if (Req->UrlRecv > Req->UrlSent) /* Waiting for connect or sending request */
{
debug ((", Write Con %d", (int) Req->ConnSocket));
FD_SET (Req->ConnSocket, WriteSet);
}
if (Req->DataRecv < MAX_DATABUFFER) /* Transfer Data */
{
debug ((", Read Con %d", (int) Req->ConnSocket));
FD_SET (Req->ConnSocket, ReadSet);
}
}
}
debug (("\n"));
}
/*)) */
/*(( "Scan/CheckUrl ()" */
/* Check whether the URL is read completely. Sets REQ_URLDONE accordingly.
* Attention! This routine relys on the fact, that ScanUrl was called already!
* That means especially, that the first line was already read completely. */
void CheckUrl (request_t *Req)
{
debug (("Checking Url\n"));
if (Req->Flags & REQ_HTTP1X0)
{
if (strstr (Req->UrlBuffer, "\n\n") || strstr (Req->UrlBuffer, "\015\015") ||
strstr (Req->UrlBuffer, "\n\015\n\015") || strstr (Req->UrlBuffer, "\015\n\015\n"))
Req->Flags |= REQ_URLDONE;
}
else
Req->Flags |= REQ_URLDONE;
#ifdef DEBUG
if (Req->Flags & REQ_URLDONE)
debug (("http request complete\n"));
#endif
}
/* Scan the URL and divide it into several parts. Understands http/0.9
* and http/1.0 versions right now. Sets REQ_URLDONE when URL is complete. */
/* Returns -1 in case of failure, 0 on correct termination or found cache,
* 1: found cache slot, FileD is open, 2: failure, cache contains error */
/* Port and Address are value-return arguments. */
int ScanUrl (request_t *Req, struct in_addr *Address, int *Port)
{
struct hostent *HostEnt;
struct in_addr In;
char BufGET [12];
char BufPROT [16];
char BufDOK [MAX_URLSAVE + 256]; /* a critical array... but that's enough */
char BufURL [128];
char BufVERS [8];
int CharsRead, CharsRead2, FoundHost = 0, i;
char *DokPtr, *PortPtr;
char *Host;
if (sscanf (Req->UrlBuffer, "%11s %15[a-zA-Z0-9]%n", BufGET, BufPROT, &CharsRead) < 2)
return (-1);
debug (("GET '%s', PROT '%s'\n", BufGET, BufPROT));
if (CharsRead >= Req->UrlRecv)
return (-1);
if (strcasecmp (BufGET, "get") != 0) /* no get command */
return (-1);
if (! ProxyProxy)
if (strcasecmp (BufPROT, "http") != 0) /* we only support http urls so far */
return (-1);
if (strncmp (& Req->UrlBuffer [CharsRead], "://", 3) != 0)
return (-1);
if (sscanf (& Req->UrlBuffer [CharsRead+3], "%126s%n", BufURL, &CharsRead2) < 1)
return (-1);
debug (("URL '%s'\n", BufURL));
CharsRead += 3 + CharsRead2;
if (! (DokPtr = strchr (BufURL, '/')) ) /* no host... */
return (-1);
*DokPtr++ = 0;
if (strcasecmp (BufPROT, "http") != 0)
*Port = -1;
else
*Port = StdHttpPort;
if ( (PortPtr = strchr (BufURL, ':')) )
{
*PortPtr = 0;
*Port = atoi (PortPtr + 1);
}
Host = BufURL;
#if (CHECK_ADDRESS != TRUE)
if (! (ProxyProxy || OffLine))
{
#endif
if ( (In.s_addr = inet_addr (BufURL)) != -1)
{
if ( (HostEnt = gethostbyaddr ((caddr_t) &In, sizeof (In), AF_INET)) )
{
debug (("gethostbyaddr () succeded\n"));
Host = HostEnt->h_name;
FoundHost = 1;
}
else
Host = inet_ntoa (In);
}
else if ( (HostEnt = gethostbyname (BufURL)) ) /* try to get unique name */
{
debug (("gethostbyname () succeded\n"));
Host = HostEnt->h_name;
In.s_addr = ((struct in_addr *)(HostEnt->h_addr)) -> s_addr;
FoundHost = 1;
}
#if (CHECK_ADDRESS != TRUE)
}
else
FoundHost = 1; /* we do know our proxy host */
#endif
*Address = In;
BufVERS [2] = 0; /* BufVERS is missused here... */
if (*Port > -1)
sprintf (BufDOK, "%s://%s:%d/", BufPROT, Host, *Port);
else
sprintf (BufDOK, "%s://%s/", BufPROT, Host);
for (PortPtr = BufDOK + strlen (BufDOK); *DokPtr; DokPtr++, PortPtr++)
{
if (*DokPtr == '%')
{
if (! (BufVERS [0] = *++DokPtr) )
break;
if (! (BufVERS [1] = *++DokPtr) )
break;
if ( (i = strtol (BufVERS, NULL, 0x10)) )
*PortPtr = i;
else
*PortPtr = '?';
}
else
*PortPtr = *DokPtr;
}
*PortPtr = 0;
debug (("Proto '%s', Host '%s', Port %d, Dok '%s'\n", BufPROT, Host, *Port, BufDOK));
if (Req->UrlBuffer [CharsRead] == ' ' && CharsRead < Req->UrlRecv)
if (sscanf (& Req->UrlBuffer [CharsRead], " %5s%*s\n", BufVERS) == 1)
if (strcasecmp (BufVERS, "http/") == 0)
{
debug (("got HTTP/1.0 or greater request\n"));
Req->Flags |= REQ_HTTP1X0;
}
CheckUrl (Req);
if (strlen (BufDOK) > MAX_URLSAVE-1) /* That one cannot be cached */
{
fprintf (LogStream, "UrlBuffer size (%d chars) exceeded - document '%s' is not cached\n", MAX_URLSAVE-1, BufDOK);
debug (("UrlBuffer size (%d chars) exceeded - document '%s' is not cached\n", MAX_URLSAVE-1, BufDOK));
return (0);
}
return (ScanCache (Req, BufDOK, FoundHost));
}
/*)) */
/*(( "DeleteRequest/Connect ()" */
/* Close the connection socket only. When a request socket is still open
* and no data is in the cache, close all.
* Delete cache entry in case of an error. Check the answer whether it is
* an error message or a regular answer. Errors should not be cached! */
void DeleteConnect (request_t *Req, int ok)
{
/* cache_t tmp;*/
debug (("DeleteCon %d\n", (int) Req->ConnSocket));
if (Req->Flags & REQ_CONNSOCKET)
{
CloseSocket (Req->ConnSocket);
Req->Flags &= ~REQ_CONNSOCKET;
if (Req->Stream)
fclose (Req->Stream);
Req->Stream = NULL;
if (ok)
{
if (Req->Cache && ! (Req->Cache->Flags & CACHE_QUEUED)) /* we've written into a cache file */
{
SaveCacheUrl (Req->Cache);
/* if (Req->Cache->Flags & CACHE_DELETETMP)
{
InitCacheSlot (&tmp, Req->Cache, '@', '@');
if (remove (tmp.File))
{
fprintf (LogStream, "error while removing temporary url file '%s': %s\n", tmp.File, strerror (errno));
debug (("error while removing temporary url file '%s': %s\n", tmp.File, strerror (errno)));
}
}
else
SaveCacheUrl (Req->Cache);*/
Req->Cache = NULL;
}
}
else if (Req->Cache) /* not ok - a error occured */
{
fprintf (LogStream, "error while receiving URL '%s' - removing\n", Req->Cache->Url);
debug (("error while receiving URL '%s' - removing\n", Req->Cache->Url));
Req->Cache->Url[0] = '\0';
RemCacheEntry (Req->Cache);
Req->Cache = NULL;
}
}
if (Req->Flags & REQ_REQSOCKET)
{
if (Req->DataRecv <= Req->DataSent)
{
CloseSocket (Req->ReqSocket); /* All done. Hugh! */
Req->Flags = 0;
}
else
Req->Flags |= REQ_DONE;
}
else
Req->Flags = 0;
if (! Req->Flags)
RequestsFree++;
}
/* Close the request socket only. When a connection socket is already up
* (and enough data is already read) continue filling up the cache. When
* everything is done, close all. The URL should be checked, too.
* When it is not complete, all connections should be terminated. */
void DeleteRequest (request_t *Req)
{
debug (("DeleteReq %d\n", (int) Req->ReqSocket));
if (Req->Flags & REQ_REQSOCKET)
{
CloseSocket (Req->ReqSocket);
Req->Flags &= ~REQ_REQSOCKET;
}
if (Req->Flags & REQ_DONE) /* Maybe we're getting data from the cache */
{
if (Req->Stream)
fclose (Req->Stream);
Req->Stream = NULL;
Req->Flags = 0; /* All done. Hugh! */
debug (("All done - deleterequest\n"));
DeleteConnect (Req, FALSE);
}
else if (Req->Flags & REQ_URLDONE)
{
if (! (Req->Flags & REQ_CONNSOCKET)) /* Aborting cache sending */
DeleteConnect (Req, FALSE);
else
Req->DataSent = Req->DataRecv = 0; /* Continue getting data into the cache */
}
else
DeleteConnect (Req, FALSE); /* No request known so far... */
}
/*)) */
/*(( "ServConnect ()" */
/* The first line of the URL is already there, so we can connect to the
* remote host. */
void ServConnect (request_t *Req)
{
struct sockaddr_in SockIn;
struct sockaddr_in *SockPtr = &SockIn;
ioctl_t on = 1;
int Port;
debug (("ServCon for Req %d; Flags %x\n", (int) Req->ReqSocket, Req->Flags));
if (Req->Flags & REQ_REQSOCKET)
{
if (strchr (Req->UrlBuffer, '\n') == 0 &&
strchr (Req->UrlBuffer, '\015') == 0) /* first line of URL is not there... */
return;
if (Req->Flags & (REQ_CONNSOCKET | REQ_DONE)) /* already set up */
{
CheckUrl (Req);
return;
}
}
memset ((char *) &SockIn, 0, sizeof (struct sockaddr_in));
switch (ScanUrl (Req, &SockIn.sin_addr, &Port)) {
case 0: /* need to get document from remote host */
if (Req->Cache)
{
fprintf (LogStream, "#%02d: getting URL '%s'\n", (int) Req->ReqSocket, Req->Cache->Url);
debug (("#%02d: getting URL '%s'\n", (int) Req->ReqSocket, Req->Cache->Url));
}
else
{
fprintf (LogStream, "#%02d: serving uncacheable request '%s'\n",
(int) Req->ReqSocket, Req->UrlBuffer);
debug (("#%02d: serving uncacheable request '%s'\n",
(int) Req->ReqSocket, Req->UrlBuffer));
}
break;
case 1: /* already cached - no need to connect to remote host */
assert (Req->Cache != NULL);
fprintf (LogStream, "#%02d: sending URL '%s' from cache '%s'\n",
(int) Req->ReqSocket, Req->Cache->Url, Req->Cache->File);
debug (("#%02d: sending URL '%s' from cache '%s'\n",
(int) Req->ReqSocket, Req->Cache->Url, Req->Cache->File));
GetCacheData (Req);
return;
case 2: /* send error message from buffer */
return;
default:
{
fprintf (LogStream, "#%02d: invalid request\n", (int) Req->ReqSocket);
debug (("#%02d: invalid request\n", (int) Req->ReqSocket));
DeleteRequest (Req);
return;
}
}
if (OffLine)
{
ErrToReq (Req, 500, "Unable to serv", "It is not possible to connect to a remote host in offline mode.<BR>\n"
"Either the requested URL is to long to be queued or an internal error has occured. When the\n"
"URL is rather short, please contact the author of httpproxy.");
return;
}
if (ProxyProxy)
SockPtr = &ProxyProxyIn;
else
{
SockIn.sin_family = AF_INET;
SockIn.sin_port = htons (Port);
}
if ( (Req->ConnSocket = socket (AF_INET, SOCK_STREAM, 0)) < 0) /* open connection */
{
syslog (LOG_ERR, "%s: socket () failed", PrgName);
DeleteRequest (Req);
return;
}
Req->Flags |= REQ_CONNSOCKET;
#ifdef FIOASYNC
if (ioctl (Req->ConnSocket, FIOASYNC, (caddr_t) &on) < 0)
{
syslog (LOG_ERR, "%s: ioctl() failed for FIOASYNC: %s", PrgName, strerror (errno));
DeleteRequest (Req);
return;
}
#endif
#ifdef FIONBIO
if (ioctl (Req->ConnSocket, FIONBIO, (caddr_t) &on) < 0)
{
syslog (LOG_ERR, "%s: ioctl() failed for FIONBIO: %s", PrgName, strerror (errno));
DeleteRequest (Req);
return;
}
#endif
if (connect (Req->ConnSocket, (struct sockaddr *) SockPtr, sizeof (struct sockaddr_in)) < 0)
{
if (errno != EINPROGRESS)
{
if (ProxyProxy)
{
fprintf (LogStream, "#%02d: proxyproxy host unreachable: %s\n", (int) Req->ReqSocket, strerror (errno));
debug (("#%02d: proxyproxy host unreachable: %s\n", (int) Req->ReqSocket, strerror (errno)));
}
else
{
fprintf (LogStream, "#%02d: remote host unreachable: %s\n", (int) Req->ReqSocket, strerror (errno));
debug (("#%02d: remote host unreachable: %s\n", (int) Req->ReqSocket, strerror (errno)));
}
DeleteRequest (Req);
return;
}
}
debug (("ServCon -> Conn %d\n", (int) Req->ConnSocket));
}
/*)) */
/*(( "ServServer ()" */
/* Serv the server port. Accept new connections, set up request database */
void ServServer (void)
{
struct hostent *Host;
struct sockaddr_in PeerIn;
len_t Len = sizeof (struct sockaddr_in);
request_t *Req = Requests;
ioctl_t on = 1;
debug (("accept Req %d\n", (int) Req->ReqSocket));
assert (RequestsFree > 0);
while (Req->Flags) /* any Flags set -> occupied */
Req++;
assert (Req - Requests < MAX_REQUESTS);
if ( (Req->ReqSocket = accept (ServerSocket, (struct sockaddr *) &PeerIn, &Len)) < 0)
{
syslog (LOG_ERR, "%s: accept() failed: %s", PrgName, strerror (errno));
return;
}
if ( (Host = gethostbyaddr ((caddr_t) &PeerIn, sizeof (PeerIn), AF_INET)) )
{
fprintf (LogStream, "#%02d: new request from %s:%d\n",
(int) Req->ReqSocket, Host->h_name, PeerIn.sin_port);
debug (("#%02d: new request from %s:%d\n",
(int) Req->ReqSocket, Host->h_name, PeerIn.sin_port));
}
else
{
fprintf (LogStream, "#%02d: new request from %s:%d\n",
(int) Req->ReqSocket, inet_ntoa (PeerIn.sin_addr), PeerIn.sin_port);
debug (("#%02d: new request from %s:%d\n",
(int) Req->ReqSocket, inet_ntoa (PeerIn.sin_addr), PeerIn.sin_port));
}
Req->Flags |= REQ_REQSOCKET;
Req->UrlSent = Req->UrlRecv = Req->DataSent = Req->DataRecv = 0;
Req->Cache = NULL;
Req->Stream = NULL;
RequestsFree--;
#ifdef FIOASYNC
if (ioctl (Req->ReqSocket, FIOASYNC, (caddr_t) &on) < 0)
{
syslog (LOG_ERR, "%s: ioctl() failed for FIOASYNC: %s", PrgName, strerror (errno));
DeleteRequest (Req);
return;
}
#endif
#ifdef FIONBIO
if (ioctl (Req->ReqSocket, FIONBIO, (caddr_t) &on) < 0)
{
syslog (LOG_ERR, "%s: ioctl() failed for FIONBIO: %s", PrgName, strerror (errno));
DeleteRequest (Req);
return;
}
#endif
}
/*)) */
/*(( "ServRead ()" */
/* Server for all reads */
void ServRead (fd_set *ReadSet)
{
int i, Bytes;
register request_t *Req;
if (FD_ISSET (ServerSocket, ReadSet))
ServServer ();
for (i=0, Req=Requests; i < MAX_REQUESTS; i++, Req++)
{
if ((Req->Flags & REQ_REQSOCKET) && FD_ISSET (Req->ReqSocket, ReadSet))
{
assert (Req->UrlRecv < MAX_URLBUFFER-1);
if ( (Bytes = recv (Req->ReqSocket, & Req->UrlBuffer [Req->UrlRecv],
(long) MAX_URLBUFFER-1 - Req->UrlRecv, 0)) < 0)
{
fprintf (LogStream, "#%02d: on receiving URL: %s\n", (int) Req->ReqSocket, strerror (errno));
debug (("#%02d: on receiving URL: %s\n", (int) Req->ReqSocket, strerror (errno)));
DeleteRequest (Req);
}
else
{
Req->UrlRecv += Bytes;
Req->UrlBuffer [Req->UrlRecv] = '\0'; /* needed for strchr() */
debug (("Read %d bytes from Req %d containing:\n'%s'\n", Bytes,
(int) Req->ReqSocket, & Req->UrlBuffer [Req->UrlRecv - Bytes]));
debug (("= 0x %08lx %08lx %08lx %08lx\n", * (long *) (Req->UrlBuffer + Req->UrlRecv - Bytes),
* (long *) (Req->UrlBuffer + Req->UrlRecv - Bytes + 4), * (long *) (Req->UrlBuffer + Req->UrlRecv - Bytes + 8),
* (long *) (Req->UrlBuffer + Req->UrlRecv - Bytes + 12)));
if (Bytes == 0) /* request socket gone away */
DeleteRequest (Req);
ServConnect (Req); /* Check if anything can be done already */
if ((Req->UrlRecv == MAX_URLBUFFER-1) && ! (Req->Flags & REQ_CONNSOCKET))
{ /* buffer full and no host specification */
fprintf (LogStream, "#%02d: URL buffer overflow\n", (int) Req->ReqSocket);
debug (("#%02d: URL buffer overflow\n", (int) Req->ReqSocket));
DeleteRequest (Req);
}
}
}
if ((Req->Flags & REQ_CONNSOCKET) && FD_ISSET (Req->ConnSocket, ReadSet))
{
assert (Req->DataRecv < MAX_DATABUFFER);
if ( (Bytes = recv (Req->ConnSocket, & Req->DataBuffer [Req->DataRecv],
(long) MAX_DATABUFFER - Req->DataRecv, 0)) < 0)
{
if (ProxyProxy)
{
fprintf (LogStream, "#%02d: proxyproxy host unreachable: %s\n", (int) Req->ReqSocket, strerror (errno));
debug (("#%02d: proxyproxy host unreachable: %s\n", (int) Req->ReqSocket, strerror (errno)));
}
else
{
fprintf (LogStream, "#%02d: on receiving data: %s\n", (int) Req->ReqSocket, strerror (errno));
debug (("#%02d: on receiving data: %s\n", (int) Req->ReqSocket, strerror (errno)));
}
DeleteConnect (Req, CacheUnreadRequests);
}
else
{
debug (("Read %d bytes from Con %d\n", Bytes, (int) Req->ConnSocket));
if (Bytes == 0) /* We're done */
DeleteConnect (Req, TRUE);
else if (Req->Stream)
fwrite (& Req->DataBuffer [Req->DataRecv], sizeof (char), /* no error checking... */
Bytes, Req->Stream);
if (Req->Flags & REQ_REQSOCKET)
Req->DataRecv += Bytes; /* else: only save cache data */
}
}
}
}
/*)) */
/*(( "ServWrite ()" */
/* Server for all writes */
void ServWrite (fd_set *WriteSet)
{
int i, Bytes;
register request_t *Req;
for (i=0, Req=Requests; i < MAX_REQUESTS; i++, Req++)
{
if ((Req->Flags & REQ_REQSOCKET) && FD_ISSET (Req->ReqSocket, WriteSet))
{
assert (Req->DataRecv > Req->DataSent);
if ( (Bytes = send (Req->ReqSocket, & Req->DataBuffer [Req->DataSent],
(long) Req->DataRecv - Req->DataSent, 0)) < 0)
{
fprintf (LogStream, "#%02d: on sending data: %s\n", (int) Req->ReqSocket, strerror (errno));
debug (("#%02d: on sending data: %s\n", (int) Req->ReqSocket, strerror (errno)));
DeleteRequest (Req);
}
else
{
debug (("Wrote %d bytes to Req %d\n", Bytes, (int) Req->ReqSocket));
Req->DataSent += Bytes;
assert (Req->DataRecv >= Req->DataSent);
if (Bytes == 0) /* request socket is unable to receive data */
{
fprintf (LogStream, "#%02d: request socket unable to receive data\n", (int) Req->ReqSocket);
debug (("#%02d: request socket unable to receive data\n", (int) Req->ReqSocket));
DeleteRequest (Req);
}
if (Req->DataSent == Req->DataRecv) /* clear / shift data buffer */
Req->DataSent = Req->DataRecv = 0;
else if (Req->DataSent > SHIFT_DATABUFFER)
{
debug (("shifting databuffer size %d by %d bytes\n", Req->DataRecv - Req->DataSent, Req->DataSent));
memmove (Req->DataBuffer, & Req->DataBuffer [Req->DataSent],
Req->DataRecv - Req->DataSent);
Req->DataRecv -= Req->DataSent;
Req->DataSent = 0;
}
if (Req->DataRecv < MAX_DATABUFFER && (Req->Flags & REQ_DONE) && Req->Stream)
GetCacheData (Req);
if (Req->DataRecv == 0 && (Req->Flags & REQ_DONE)) /* We're already done */
DeleteRequest (Req);
}
}
if ((Req->Flags & REQ_CONNSOCKET) && FD_ISSET (Req->ConnSocket, WriteSet))
{
assert (Req->UrlRecv > Req->UrlSent);
if ( (Bytes = send (Req->ConnSocket, & Req->UrlBuffer [Req->UrlSent],
(long) Req->UrlRecv - Req->UrlSent, 0)) < 0)
{
if (ProxyProxy)
{
fprintf (LogStream, "#%02d: proxyproxy host unreachable: %s\n", (int) Req->ReqSocket, strerror (errno));
debug (("#%02d: proxyproxy host unreachable: %s\n", (int) Req->ReqSocket, strerror (errno)));
}
else
{
fprintf (LogStream, "#%02d: on sending url: %s\n", (int) Req->ReqSocket, strerror (errno));
debug (("#%02d: on sending url: %s\n", (int) Req->ReqSocket, strerror (errno)));
}
DeleteConnect (Req, FALSE);
}
else
{
debug (("Wrote %d bytes to Conn %d\n", Bytes, (int) Req->ConnSocket));
Req->UrlSent += Bytes;
if (Bytes == 0) /* connection socket is unable to receive url */
{
fprintf (LogStream, "#%02d: connection socket unable to receive url\n", (int) Req->ReqSocket);
debug (("#%02d: connection socket unable to receive url\n", (int) Req->ReqSocket));
DeleteConnect (Req, FALSE);
}
}
}
}
}
/*)) */
/*(( "CacheInUse()/RequestQueued()" */
/* Check wheather a cache entry is used in any other request */
request_t *CacheInUse (cache_t *c)
{
request_t *Req = Requests;
int i;
for (i = 0; i < MAX_REQUESTS; i++, Req++)
if (Req->Cache == c)
return (Req);
return (NULL);
}
/* Initiate contact to remote host for a queued request */
void RequestQueued (cache_t *c)
{
request_t *Req = Requests;
char Buffer [MAX_URLBUFFER];
char *p, *cp;
assert (MAX_URLBUFFER >= 4 * MAX_URLSAVE); /* to be on the save side */
fprintf (LogStream, "initiating request for Url '%s'\n", c->Url);
while (Req->Flags) /* any Flags set -> occupied */
Req++;
assert (Req - Requests < MAX_REQUESTS);
Req->Flags = REQ_URLDONE;
Req->UrlSent= Req->DataSent = Req->DataRecv = 0;
Req->Cache = NULL;
Req->Stream = NULL;
RequestsFree--;
for (p = c->Url, cp = Buffer; *p; )
if (isvalidhttp (*p))
*cp++ = *p++;
else
{
sprintf (cp, "%%%02x", *p++);
cp += 3;
}
*cp = '\0';
sprintf (Req->UrlBuffer, "GET %s HTTP/1.0\nUser-Agent: %s\nAccept: */*\n\n", Buffer, Version);
Req->UrlRecv = strlen (Req->UrlBuffer);
debug (("initiating request '%s'\n", Req->UrlBuffer));
if (c->File [0] == '@')
c->Url [0] = '\0';
RemCacheEntry (c);
ServConnect (Req);
}
/*)) */
/*(( "CheckGetQueued()" */
/* Check number of pending requests and initiate getting of queued URLs */
void CheckGetQueued (void)
{
static int Nr = 0;
static int State = 1;
int Found = 1;
cache_t *c;
if (State == 3 || ! GetQueued)
return;
while (Found && RequestsFree > MIN_REQUESTS)
{
Found = 0;
debug (("State %d\n", State));
switch (State) {
case 1: /* get all CACHE_QUEUED & ! CACHE_VALID */
for (c = & Caches [Nr]; Nr < MAX_CACHES; Nr++, c++)
if ((c->Flags & CACHE_QUEUED) && ! (c->Flags & CACHE_VALID)) /* get urls without expired caches first */
{
Found++;
if (! CacheInUse (c))
{
RequestQueued (c);
break;
}
}
if (Nr == MAX_CACHES)
Nr = 0;
if (Found)
break;
Nr = 0; /* no break! */
State = 2;
case 2:
for (c = & Caches [Nr]; Nr < MAX_CACHES; Nr++, c++)
if (c->Flags & CACHE_QUEUED)
{
assert (c->Flags & CACHE_VALID);
Found++;
if (! CacheInUse (c))
{
RequestQueued (c);
break;
}
}
if (! Found)
State = 3;
break;
default:
assert (0);
}
}
}
/*)) */
/*(( "DeleteInvalidCaches()" */
/* Shutdown: Close and delete all invalid cache entries. The cache database will be
* in an inconsistent state after this operation... (missing queue entries) */
void DeleteInvalidCaches (void)
{
cache_t *c;
request_t *Req;
int i;
for (i = 0, c = Caches; i < MAX_CACHES; i++, c++)
if (c->File[0] && (c->Flags & (CACHE_VALID | CACHE_QUEUED)) +0 == 0) /* !!! */
{
Req = CacheInUse (c);
if (Req)
{
if (Req->Stream) /* close cache stream */
fclose (Req->Stream);
Req->Stream = NULL;
if (c->Url[0])
{
fprintf (LogStream, "removing and queueing URL '%s'\n", c->Url);
SaveCacheUrl (c);
}
c->Url[0] = '\0'; /* keep the Url file (cache is queued) */
RemCacheEntry (c); /* delete cache entry, data file and intermediate url files */
}
else
debug (("File '%s', Url '%s', Flags 0x%x without Request!\n", c->File, c->Url, c->Flags));
}
}
/*)) */
/*(( "main ()" */
/* The main routine */
void main (int argc, char **argv)
{
fd_set ReadSet;
fd_set WriteSet;
char *ProxyHost = NULL;
char *LogFile = ".httpproxy-log";
int ProxyPort;
PrgName = *argv++;
if (--argc == 1)
if (**argv == '?')
{
fprintf (stderr, "Usage: %s [proxy PROXYHOST PROXYPORT] [port PORT] [cache DIR] [del SECONDS]\n"
"[expire SECONDS] [reload SECONDS] [log FILE] [unread] [offline] [get]\n"
"The cache keyword will change the local directory.\n", PrgName);
exit (0);
}
while (argc)
{
if (strcasecmp (*argv, "proxy") == 0)
{
if (argc < 3)
{
fprintf (stderr, "%s: need two arguments for 'proxy'\n", PrgName);
exit (1);
}
argv++;
argc -= 3;
ProxyHost = *argv++;
ProxyPort = atoi (*argv++);
}
else if (strcasecmp (*argv, "port") == 0)
{
if (argc < 2)
{
fprintf (stderr, "%s: need a argument for 'port'\n", PrgName);
exit (1);
}
argv++;
argc -= 2;
ServerPort = atoi (*argv++);
}
else if (strcasecmp (*argv, "cache") == 0)
{
if (argc < 2)
{
fprintf (stderr, "%s: need a argument for 'cache'\n", PrgName);
exit (1);
}
argv++;
argc -= 2;
if (chdir (*argv++))
{
fprintf (stderr, "%s: no directory '%s': %s\n", PrgName, argv[-1], strerror (errno));
exit (1);
}
}
else if (strcasecmp (*argv, "del") == 0)
{
if (argc < 2)
{
fprintf (stderr, "%s: need a argument for 'del'\n", PrgName);
exit (1);
}
argv++;
argc -= 2;
DelCacheTime = atoi (*argv++);
}
else if (strcasecmp (*argv, "expire") == 0)
{
if (argc < 2)
{
fprintf (stderr, "%s: need a argument for 'expire'\n", PrgName);
exit (1);
}
argv++;
argc -= 2;
ExpireCacheTime = atoi (*argv++);
}
else if (strcasecmp (*argv, "reload") == 0)
{
if (argc < 2)
{
fprintf (stderr, "%s: need a argument for 'reload'\n", PrgName);
exit (1);
}
argv++;
argc -= 2;
ReloadCacheTime = atoi (*argv++);
}
else if (strcasecmp (*argv, "log") == 0)
{
if (argc < 2)
{
fprintf (stderr, "%s: need a argument for 'log'\n", PrgName);
exit (1);
}
argv++;
argc -= 2;
LogFile = *argv++;
}
else if (strcasecmp (*argv, "unread") == 0)
{
argv++;
argc--;
CacheUnreadRequests = TRUE;
}
else if (strcasecmp (*argv, "offline") == 0)
{
argv++;
argc--;
if (GetQueued)
{
fprintf (stderr, "%s: you cannot specify both, 'get' and 'offline'", PrgName);
exit (1);
}
OffLine = 1;
}
else if (strcasecmp (*argv, "get") == 0)
{
argv++;
argc--;
if (OffLine)
{
fprintf (stderr, "%s: you cannot specify both, 'get' and 'offline'", PrgName);
exit (1);
}
GetQueued = 1;
}
else
{
fprintf (stderr, "%s: unknown option '%s'\n", PrgName, *argv);
exit (1);
}
}
Init (ProxyHost, ProxyPort, LogFile);
BuildCache ();
assert (FD_SETSIZE > MAX_REQUESTS * 3);
/* Never return */
for (;;)
{
CheckGetQueued ();
BuildFdSets (&ReadSet, &WriteSet);
debug (("Select... RequestsFree %d, CachesFree %d\n", RequestsFree, CachesFree));
if (select (FD_SETSIZE, &ReadSet, &WriteSet, NULL, NULL) <= 0)
{
if (errno == EINTR)
{
fprintf (stderr, "%s: terminating due to signal\n", PrgName);
fprintf (LogStream, "terminating due to signal\n");
DeleteInvalidCaches ();
fprintf (LogStream, "\n");
exit (0);
}
syslog (LOG_ERR, "%s: select failed: %s", PrgName, strerror (errno));
}
else
{
ServRead (&ReadSet);
ServWrite (&WriteSet);
}
}
}
/*)) */